﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;

#endregion

namespace Vk.Methods.Messages
{
    /// <summary>
    ///     Сообщения
    /// </summary>
    public class Messages
    {
        private readonly Api _api;

        /// <summary>
        ///     Количество сообщений в истории
        /// </summary>
        public short HistoryMessagesCount { get; set; }

        /// <summary>
        ///     Количествов входящих сообщений
        /// </summary>
        public short IncomingMessagesCount { get; set; }

        /// <summary>
        ///     Количество исходящих сообщений
        /// </summary>
        public short OutgoingMessagesCount { get; set; }

        /// <summary>
        ///     Количествов непрочитанных диалогов
        /// </summary>
        public short UnreadDialogs { get; set; }

        public Messages(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Добавляет в мультидиалог нового пользователя.
        /// </summary>
        /// <param name="chatId">ID беседы, в которую необходимо добавить пользователя</param>
        /// <param name="userId">ID беседы, в которую необходимо добавить пользователя</param>
        /// <returns>После успешного выполнения возвращает true </returns>
        public async Task<bool> AddChatUser(int chatId, int userId)
        {
            var query = new ApiQuery("messages.addChatUser")
                .Add("chat_id", chatId)
                .Add("user_id", userId);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return resp.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Создаёт беседу с несколькими участниками.
        /// </summary>
        /// <param name="userIds">Список идентификаторов друзей текущего пользователя с которыми необходимо создать беседу.</param>
        /// <param name="title">Название мультидиалога.</param>
        /// <returns>Возвращает идентификатор созданного чата в случае успешного выполнения данного метода.</returns>
        public async Task<int> CreateChat(IEnumerable<int> userIds, string title)
        {
            if (userIds == null)
            {
                throw new ArgumentNullException(nameof(userIds));
            }
            if (title == null)
            {
                throw new ArgumentNullException(nameof(title));
            }

            var query = new ApiQuery("messages.createChat")
                .Add("title", title)
                .Add("user_ids", userIds);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Int().HasValue ? resp.Int().Value : -1;
        }

        /// <summary>
        ///     Удаляет сообщение.
        /// </summary>
        /// <param name="messageIds">Список идентификаторов сообщений, разделённых через запятую.</param>
        /// <returns>Возвращает true в случае успешного удаления или код ошибки. </returns>
        public async Task<bool> Delete(IEnumerable<int> messageIds)
        {
            if (messageIds == null)
            {
                throw new ArgumentNullException(nameof(messageIds));
            }

            var query = new ApiQuery("messages.delete")
                .Add("message_ids", messageIds);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return resp.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Позволяет удалить фотографию мультидиалога
        /// </summary>
        /// <param name="chatId">Идентификатор беседы</param>
        public async Task<ChatPhotoResult> DeleteChatPhoto(int chatId)
        {
            var query = new ApiQuery("messages.deleteChatPhoto")
                .Add("chat_id", chatId);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any()
                           ? null
                           : new ChatPhotoResult(resp);
            }
            return null;
        }

        /// <summary>
        ///     Удаляет все личные сообщения в диалоге.
        /// </summary>
        /// <param name="userId">ID пользователя.</param>
        /// <param name="peerId">
        ///     Идентификатор назначения.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <param name="offset">
        ///     Начиная с какого сообщения нужно удалить переписку. (По умолчанию удаляются все сообщения начиная
        ///     с первого).
        /// </param>
        /// <param name="count">
        ///     Как много сообщений нужно удалить. Обратите внимание что на метод наложено ограничение, за один
        ///     вызов нельзя удалить больше 10000 сообщений, поэтому если сообщений в переписке больше - метод нужно вызывать
        ///     несколько раз.
        /// </param>
        /// <returns>Возвращает true в случае успешного удаления. </returns>
        public async Task<bool> DeleteDialog(int? userId = null,
                                             int? peerId = null,
                                             int? offset = null,
                                             int? count = null)
        {
            var query = new ApiQuery("messages.deleteDialog")
                .Add("user_id", userId)
                .Add("peer_id", peerId)
                .Add("offset", offset)
                .Add("count", count);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Изменяет название беседы.
        /// </summary>
        /// <param name="chatId">Идентификатор чата</param>
        /// <param name="title">Название беседы.</param>
        public async Task<bool> EditChat(int chatId, string title)
        {
            if (title == null)
            {
                throw new ArgumentNullException(nameof(title));
            }

            var query = new ApiQuery("messages.createChat")
                .Add("title", title)
                .Add("chat_id", chatId);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any() ? false : resp.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Возвращает список входящих либо исходящих личных сообщений текущего пользователя
        /// </summary>
        /// <param name="out">Если этот параметр равен 1, сервер вернет исходящие сообщения.</param>
        /// <param name="filters">
        ///     Фильтр возвращаемых сообщений: 1 - только непрочитанные; 2 - не из чата; 4 - сообщения от друзей.
        ///     Если установлен флаг "4", то флаги "1" и "2" не учитываются
        /// </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества сообщений</param>
        /// <param name="count">Количество сообщений, которое необходимо получить (но не более 100)</param>
        /// <param name="previewLength">
        ///     Количество символов, по которому нужно обрезать сообщение. Укажите 0, если Вы не хотите
        ///     обрезать сообщение. (по умолчанию сообщения не обрезаются). Обратите внимание что сообщения обрезаются по словам
        /// </param>
        /// <param name="timeOffset">
        ///     Максимальное время, прошедшее с момента отправки сообщения до текущего момента в секундах. 0,
        ///     если Вы хотите получить сообщения любой давности
        /// </param>
        /// <param name="lastMessageId">
        ///     Идентификатор сообщения, полученного перед тем, которое нужно вернуть последним (при
        ///     условии, что после него было получено не более count сообщений, иначе необходимо использовать с параметром offset)
        /// </param>
        public async Task<ListCount<Message>> Get(MessageType @out,
                                                  MessageFilter? filters = null,
                                                  int? offset = null,
                                                  int? count = null,
                                                  int? lastMessageId = null,
                                                  int? previewLength = null,
                                                  int? timeOffset = null)
        {
            var query = new ApiQuery("messages.get")
                .Add("out", (int) @out)
                .Add("filters", (int) filters)
                .Add("offset", offset)
                .Add("count", count)
                .Add("preview_length", previewLength)
                .Add("time_offset", timeOffset)
                .Add("last_message_id", lastMessageId);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any()
                           ? null
                           : new ListCount<Message>(resp.Int("count").Value, BuildMessagesList(resp, @out));
            }
            return null;
        }

        /// <summary>
        ///     Возвращает сообщения по их ID.
        /// </summary>
        /// <param name="messageIds">ID сообщений, которые необходимо вернуть, разделенные запятыми (не более 100).</param>
        /// <param name="previewLength">
        ///     Количество слов, по которому нужно обрезать сообщение. Укажите 0, если Вы не хотите
        ///     обрезать сообщение. (по умолчанию сообщения не обрезаются).
        /// </param>
        public async Task<ListCount<Message>> GetById(IEnumerable<int> messageIds, int? previewLength = null)
        {
            var query = new ApiQuery("messages.getById")
                .Add("message_ids", messageIds)
                .Add("preview_length", previewLength);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return resp.ChildNodes.Any()
                           ? null
                           : new ListCount<Message>(resp.Int("count").Value,
                                                    BuildMessagesList(resp, MessageType.Dialogs));
            }
            return null;
        }

        /// <summary>
        ///     Возвращает информацию о беседе
        /// </summary>
        /// <param name="chatId">Идентификатор беседы</param>
        /// <param name="chatIds">Список идентификаторов бесед</param>
        /// <param name="fields">Список дополнительных полей профилей, которые необходимо вернуть</param>
        /// <param name="nameCase">
        ///     Падеж для склонения имени и фамилии пользователя. Возможные значения: именительный – nom,
        ///     родительный – gen, дательный – dat, винительный – acc, творительный – ins, предложный – abl. По умолчанию nom
        /// </param>
        public async Task<List<ChatInfo>> GetChat(int? chatId = null,
                                                  IEnumerable<int> chatIds = null,
                                                  IEnumerable<string> fields = null,
                                                  string nameCase = null)
        {
            var query = new ApiQuery("messages.getChat")
                .Add("chat_id", chatId)
                .Add("chat_ids", chatIds)
                .Add("fields", fields)
                .Add("name_case", nameCase);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                var nodes = resp.SelectNodes("//chat");
                if (nodes != null && nodes.Any())
                    return nodes.Select(x => new ChatInfo(x)).ToList();
            }
            return null;
        }

        /// <summary>
        ///     Позволяет получить список пользователей мультидиалога по его id.
        /// </summary>
        /// <param name="chatId">Идентификатор беседы</param>
        /// <param name="chatIds">Идентификаторы бесед</param>
        /// <param name="fields">Список дополнительных полей профилей, которые необходимо вернуть</param>
        /// <param name="nameCase">
        ///     Падеж для склонения имени и фамилии пользователя. Возможные значения: именительный – nom,
        ///     родительный – gen, дательный – dat, винительный – acc, творительный – ins, предложный – abl. По умолчанию nom.
        /// </param>
        public async Task<List<ChatUser>> GetChatUsers(int chatId,
                                                       IEnumerable<int> chatIds = null,
                                                       IEnumerable<string> fields = null,
                                                       string nameCase = null)
        {
            var query = new ApiQuery("messages.getChatUsers")
                .Add("chat_id", chatId)
                .Add("fields", fields)
                .Add("chat_ids", chatIds)
                .Add("name_case", nameCase);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                if (!resp.ChildNodes.Any())
                {
                    return null;
                }

                var nodes = resp.SelectNodes("user");
                if (nodes != null && nodes.Any())
                {
                    return
                        nodes
                            .Select(y => new ChatUser(y))
                            .ToList();
                }
                nodes = resp.SelectNodes("user_id");
                if (nodes != null && nodes.Any())
                {
                    return nodes.Select(x => x.Int()).Where(x => x.HasValue).Select(y => new ChatUser(y.Value)).ToList();
                }
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список диалогов текущего пользователя.
        /// </summary>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества диалогов.</param>
        /// <param name="count">Количество диалогов, которое необходимо получить (но не более 200).</param>
        /// <param name="previewLength">
        ///     Количество символов, по которому нужно обрезать сообщение. Укажите 0, если Вы не хотите
        ///     обрезать сообщение. (по умолчанию сообщения не обрезаются).
        /// </param>
        /// <param name="unread">
        ///     Значение true означает, что нужно вернуть только диалоги в которых есть непрочитанные входящие
        ///     сообщения. По умолчанию false.
        /// </param>
        /// <param name="startMessageId">
        ///     Идентификатор сообщения, начиная с которого нужно вернуть список диалогов (подробности см.
        ///     ниже).
        /// </param>
        public async Task<GetDialogsResult> GetDialogs(bool? unread = null,
                                                       int? offset = null,
                                                       int? count = null,
                                                       int? previewLength = null,
                                                       int? startMessageId = null)
        {
            var query = new ApiQuery("messages.getDialogs")
                .Add("unread", unread)
                .Add("offset", offset)
                .Add("count", count)
                .Add("start_message_id", startMessageId)
                .Add("preview_length", previewLength);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new GetDialogsResult(resp) : null;
        }

        /// <summary>
        ///     Возвращает историю сообщений для указанного пользователя
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя, историю переписки с которым необходимо вернуть. Является
        ///     необязательным параметром в случае с истории сообщений в беседе.
        /// </param>
        /// <param name="peerId">
        ///     Для пользователя: id пользователя.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <param name="offset">
        ///     Смещение, необходимое для выборки определенного подмножества сообщений, должен быть больше, равно
        ///     0, если не передан параметр start_message_id, и должен быть менее или равно 0, если передан.
        /// </param>
        /// <param name="count">Количество сообщений, которое необходимо получить (но не более 200).</param>
        /// <param name="startMessageId">
        ///     Если значение > 0, то это идентификатор сообщения, начиная с которого нужно вернуть историю
        ///     переписки, если же передано значение -1, то к значению параметра offset прибавляется количество входящих
        ///     непрочитанных сообщений в конце диалога
        /// </param>
        /// <param name="rev">
        ///     true – возвращать сообщения в хронологическом порядке. false – возвращать сообщения в обратном
        ///     хронологическом порядке (по умолчанию), недоступен при переданном start_message_id.
        /// </param>
        /// <param name="chatId">Идентификатор диалога, историю сообщений которого необходимо получить. </param>
        /// <reremarks>
        ///     Параметр start_message_id вместе с offset менее или равно 0 и count больше 0 позволяет получить интервал истории
        ///     сообщений вокруг данного сообщения или вокруг начала отрезка непрочитанных входящих сообщений.
        ///     Для start_message_id > 0 к значению параметра offset прибавляется количество сообщений, чей идентификатор строго
        ///     больше данного start_message_id (при offset равном 0 вернутся сообщения начиная с данного включительно и более
        ///     старые, count штук).
        ///     Для start_message_id = -1 поведение такое же, как при start_message_id равном последнему сообщению в истории
        ///     переписки, не являющемуся входящим непрочитанным (при отсутствии входящих непрочитанных в этом диалоге это
        ///     совпадает с отсутствием параметра start_message_id), то есть к значению offset прибавляется количество входящих
        ///     непрочитанных сообщений в конце истории.
        /// </reremarks>
        public async Task<GetHistoryResult> GetHistory(int? userId = null,
                                                       int? chatId = null,
                                                       int? peerId = null,
                                                       int? offset = null,
                                                       int? count = null,
                                                       int? startMessageId = null,
                                                       bool? rev = null)
        {
            var query = new ApiQuery("messages.getHistory")
                .Add("user_id", userId)
                .Add("chat_id", chatId)
                .Add("peer_id", peerId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("start_message_id", startMessageId)
                .Add("rev", rev);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new GetHistoryResult(resp) : null;
        }

        /// <summary>
        ///     Возвращает материалы диалога или беседы.
        /// </summary>
        /// <param name="peerId">
        ///     Для пользователя: id пользователя.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <param name="mediaType">Тип материалов, который необходимо вернуть. </param>
        /// <param name="startFrom">Смещение, необходимое для выборки определенного подмножества объектов. </param>
        /// <param name="count">Количество объектов, которое необходимо получить (но не более 200). </param>
        /// <param name="photoSizes">
        ///     Параметр, указывающий нужно ли возвращать ли доступные размеры фотографии в специальном
        ///     формате.
        /// </param>
        /// <returns>
        ///     После успешного выполнения возвращает массив объектов photo, video, audio или doc, в зависимости от значения
        ///     media_type, а также дополнительное поле next_from, содержащее новое значение start_from.
        /// </returns>
        public async Task<HistoryAttchments> GetHistoryAttachments(int peerId,
                                                                   ApiStringEnum<MediaType> mediaType,
                                                                   string startFrom = null,
                                                                   int? count = null,
                                                                   bool? photoSizes = null,
                                                                   IEnumerable<string> fields = null)
        {
            if (mediaType == null)
                throw new ArgumentNullException(nameof(mediaType));

            var query = new ApiQuery("messages.getHistoryAttachments")
                .Add("peer_id", peerId)
                .Add("media_type", mediaType)
                .Add("count", count)
                .Add("start_from", startFrom)
                .Add("photo_sizes", photoSizes)
                .Add("fields", fields);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new HistoryAttchments(resp, mediaType.Enumeration) : null;
        }

        /// <summary>
        ///     Возвращает текущий статус и дату последней активности указанного пользователя.
        /// </summary>
        /// <param name="userId">ID пользователя, для которого нужно получить время активности.</param>
        public async Task<ActivityInfo> GetLastActivity(int userId)
        {
            var query = new ApiQuery("messages.getLastActivity")
                .Add("user_id", userId);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any() ? null : new ActivityInfo(resp);
            }
            return null;
        }

        /// <summary>
        ///     Возвращает обновления в личных сообщениях пользователя.
        ///     Для ускорения работы с личными сообщениями может быть полезно кешировать уже загруженные ранее сообщения на
        ///     мобильном устройстве / ПК пользователя, чтобы не получать их повторно при каждом обращении. Этот метод помогает
        ///     осуществить синхронизацию локальной копии списка сообщений с актуальной версией.
        /// </summary>
        /// <param name="ts">
        ///     Последнее значение параметра ts, полученное от Long Poll сервера или с помощью метода
        ///     messages.getLongPollServer
        /// </param>
        /// <param name="pts"></param>
        /// <param name="previewLength"></param>
        /// <param name="onlines"></param>
        /// <param name="eventsLimit"></param>
        /// <param name="msgsLimit"></param>
        /// <param name="maxMsgId">
        ///     Максимальный идентификатор сообщения среди уже имеющихся в локальной копии. Необходимо
        ///     учитывать как сообщения, полученные через методы API (например messages.getDialogs, messages.getHistory), так и
        ///     данные, полученные из Long Poll сервера (события с кодом 4).
        /// </param>
        /// <param name="fields">Список дополнительных полей профилей, которые необходимо вернуть.</param>
        /// <returns>Возвращает объект, который содержит поля history и messages. </returns>
        public async Task<LongPollServerHistory> GetLongPollHistory(int ts,
                                                                    int pts,
                                                                    int? previewLength = null,
                                                                    bool? onlines = null,
                                                                    int? eventsLimit = null,
                                                                    int? msgsLimit = null,
                                                                    int? maxMsgId = null,
                                                                    IEnumerable<string> fields = null)
        {
            var query = new ApiQuery("messages.getLongPollHistory")
                .Add("ts", ts)
                .Add("pts", pts)
                .Add("preview_length", previewLength)
                .Add("onlines", onlines)
                .Add("events_limit", eventsLimit)
                .Add("fields", fields)
                .Add("msgs_limit", msgsLimit)
                .Add("max_msg_id", maxMsgId);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any()
                           ? null
                           : new LongPollServerHistory(resp);
            }
            return null;
        }

        /// <summary>
        ///     Возвращает данные, необходимые для подключения к Long Poll серверу.
        ///     Long Poll подключение позволит Вам моментально узнавать о приходе новых сообщений и других событий.
        /// </summary>
        /// <param name="useSsl">true — использовать SSL. </param>
        /// <param name="needPts">true — возвращать поле pts, необходимое для работы метода messages.getLongPollHistory </param>
        public async Task<LongPollServerConnectionInfo> GetLongPollServer(bool? useSsl = null, bool? needPts = null)
        {
            var query = new ApiQuery("messages.getLongPollServer")
                .Add("use_ssl", useSsl)
                .Add("need_pts", needPts);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return resp.ChildNodes.Any()
                           ? new LongPollServerConnectionInfo(resp)
                           : null;
            }
            return null;
        }

        /// <summary>
        ///     Помечает сообщения как важные либо снимает отметку
        /// </summary>
        /// <param name="messageIds">Список идентификаторов сообщений, которые необходимо пометить</param>
        /// <param name="important">
        ///     true, если сообщения необходимо пометить, как важные;
        ///     false, если необходимо снять пометку
        /// </param>
        /// <returns></returns>
        public async Task<bool> MarkAsImportant(IEnumerable<int> messageIds, bool important = true)
        {
            var query = new ApiQuery("messages.markAsImportant")
                .Add("message_ids", messageIds)
                .Add("important", important);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return resp.Bool().Value;
            }
            return false;
        }

        /// <summary>
        ///     Помечает сообщения, как непрочитанные. С версии 5.10 не поддерживается
        /// </summary>
        /// <param name="mids">Cписок идентификаторов сообщений, разделенных запятой.</param>
        /// <returns>Возвращает true в случае успешной установки</returns>
        public async Task<bool> MarkAsNew(IEnumerable<int> mids)
        {
            var query = new ApiQuery("messages.markAsNew")
                .Add("message_ids", mids.Select(x => x.ToString()).Aggregate((a, b) => a + "," + b));

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Помечает сообщения, как прочитанные.
        /// </summary>
        /// <param name="messageIds">Список идентификаторов сообщений, разделенных запятой</param>
        /// <param name="startMessageId">
        ///     При передаче этого параметра будут помечены как прочитанные все сообщения, начиная с
        ///     данного.
        /// </param>
        /// <param name="peerId">
        ///     Идентификатор назначения.
        ///     Для пользователя: id пользователя.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <returns>Возвращает true в случае успешной установки</returns>
        public async Task<bool> MarkAsRead(IEnumerable<int> messageIds,
                                           int? peerId = null,
                                           int? startMessageId = null)
        {
            var query = new ApiQuery("messages.markAsRead")
                .Add("peer_id", peerId)
                .Add("start_message_id", startMessageId)
                .Add("message_ids", messageIds);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Исключает из мультидиалога пользователя, если текущий пользователь был создателем беседы либо пригласил
        ///     исключаемого пользователя. Также может быть использован для выхода текущего пользователя из беседы, в которой он
        ///     состоит.
        /// </summary>
        /// <param name="chatId">ID беседы, из которой необходимо удалить пользователя.</param>
        /// <param name="userId">ID пользователя.</param>
        /// <returns>Возвращает true или код ошибки. </returns>
        public async Task<bool> RemoveChatUser(int chatId, int userId)
        {
            var query = new ApiQuery("messages.removeChatUser")
                .Add("chat_id", chatId)
                .Add("user_id", userId);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Восстанавливает удаленное сообщение
        /// </summary>
        /// <param name="messageId">Идентификатор сообщения, которое нужно восстановить</param>
        /// <returns>После успешного выполнения возвращает true</returns>
        public async Task<bool> Restore(int messageId)
        {
            var query = new ApiQuery("messages.restore")
                .Add("message_id", messageId);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Возвращает список найденных личных сообщений текущего пользователя по введенной строке поиска.
        /// </summary>
        /// <param name="q">Подстрока, по которой будет производиться поиск.</param>
        /// <param name="previewLength">
        ///     Количество символов, по которому нужно обрезать сообщение. Укажите '0', если Вы не хотите
        ///     обрезать сообщение
        /// </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества сообщений из списка найденных.</param>
        /// <param name="count">Количество сообщений, которое необходимо получить (но не более 100).</param>
        /// <param name="peerId">
        ///     Идентификатор назначения.
        ///     Для пользователя: id пользователя.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <param name="date">
        ///     Дата в формате DDMMYYYY — если параметр задан, в ответе будут только сообщения, отправленные до
        ///     указанной даты.
        /// </param>
        public async Task<ListCount<Message>> Search(string q,
                                                     int? previewLength = null,
                                                     int? offset = null,
                                                     int? count = null,
                                                     int? peerId = null,
                                                     DateTime? date = null)
        {
            var apiQuery = new ApiQuery("messages.search")
                .Add("q", q)
                .Add("offset", offset)
                .Add("count", count)
                .Add("peer_id", peerId)
                .Add("date", date?.ToString("ddMMyyyy"))
                .Add("preview_length", previewLength);

            var resp = await _api.Manager.Execute(apiQuery);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any()
                           ? null
                           : new ListCount<Message>(resp.Int("count").Value,
                                                    BuildMessagesList(resp, MessageType.Dialogs));
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список найденных диалогов текущего пользователя по введенной строке поиска.
        /// </summary>
        /// <param name="q">Подстрока, по которой будет производиться поиск.</param>
        /// <param name="limit"></param>
        /// <param name="fields">Поля профилей собеседников, которые необходимо вернуть.</param>
        public async Task<List<DialogSearchItem>> SearchDialogs(string q,
                                                                int? limit = null,
                                                                IEnumerable<string> fields = null)
        {
            var apiQuery = new ApiQuery("messages.searchDialogs")
                .Add("q", q)
                .Add("limit", limit)
                .Add("fields", fields);

            var resp = await _api.Manager.Execute(apiQuery);
            if (_api.Manager.MethodSuccessed)
            {
                if (!resp.ChildNodes.Any())
                {
                    return null;
                }

                var nodes = resp.SelectNodes("item");
                if (nodes != null && nodes.Any())
                {
                    return
                        nodes
                            .Select(y => new DialogSearchItem(y))
                            .ToList();
                }
            }
            return null;
        }

        /// <summary>
        ///     Посылает сообщение.
        /// </summary>
        /// <param name="userId">ID пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="chatId">ID беседы, к которой будет относиться сообщение</param>
        /// <param name="message">Текст личного cообщения (является обязательным, если не задан параметр attachment)</param>
        /// <param name="title">Заголовок сообщения.</param>
        /// <param name="type">false - обычное сообщение, true - сообщение из чата. (по умолчанию false)</param>
        /// <param name="attachment">Медиа-приложения к личному сообщению, перечисленные через запятую.</param>
        /// <param name="forwardMessages">Идентификаторы пересылаемых сообщений, перечисленные через запятую. </param>
        /// <param name="lat">Latitude, широта при добавлении местоположения.</param>
        /// <param name="long">Longitude, долгота при добавлении местоположения.</param>
        /// <param name="domain">Короткий адрес пользователя (например, illarionov)</param>
        /// <param name="userIds">Идентификаторы получателей сообщения (при необходимости создать новую беседу)</param>
        /// <param name="stickerId">Идентификатор стикера.</param>
        /// <param name="peerId">
        ///     Идентификатор назначения.
        ///     Для пользователя: id пользователя.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <param name="randomId">
        ///     Уникальный идентификатор, предназначенный для предотвращения повторной отправки одинакового
        ///     сообщения. Сохраняется вместе с сообщением и доступен в истории сообщений.
        /// </param>
        /// <param name="notification">
        ///     Сообщение является уведомлением (true — да, false — нет). Используется при отправке
        ///     сообщений от имени сообщества.
        /// </param>
        public async Task<int> Send(int? userId,
                                    int? chatId,
                                    string domain = null,
                                    IEnumerable<int> userIds = null,
                                    string message = null,
                                    string title = null,
                                    SendMessageType? type = null,
                                    IEnumerable<MessageAttachment> attachment = null,
                                    IEnumerable<int> forwardMessages = null,
                                    int? lat = null,
                                    int? @long = null,
                                    int? stickerId = null,
                                    int? peerId = null,
                                    int? randomId = null,
                                    bool? notification = null)
        {
            if (userId == null & chatId == null & userIds != null)
            {
                throw new ArgumentException(
                    "Невозможно выполнить метод, если не указан хотя бы один с параметров: user_id, chat_id, user_ids");
            }

            if (message != null & attachment != null)
            {
                throw new ArgumentException(
                    "Невозможно выполнить метод с одновременно указанными параметрами: message, attachments");
            }
            if (message == null & attachment == null)
            {
                throw new ArgumentException(
                    "Невозможно выполнить метод, если не указан хотя бы один с параметров: message, attachments");
            }

            var query = new ApiQuery("messages.send")
                .Add("user_id", userId)
                .Add("chat_id", chatId)
                .Add("sticker_id", stickerId)
                .Add("domain", domain)
                .Add("user_ids", userIds)
                .Add("message", message)
                .Add("forward_messages", forwardMessages)
                .Add("lat", lat)
                .Add("long", @long)
                .Add("attachment", attachment)
                .Add("title", title)
                .Add("peer_id", peerId)
                .Add("random_id", randomId)
                .Add("notification", notification)
                .Add("type", (int) type);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Int().HasValue ? resp.Int().Value : -1;
        }

        /// <summary>
        ///     Изменяет статус набора текста пользователем в диалоге.
        /// </summary>
        /// <param name="type">typing - пользователь начал набирать текст</param>
        /// <param name="userId">ID пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="peerId">
        ///     Идентификатор назначения.
        ///     Для групповой беседы: 2000000000 + id беседы.
        ///     Для сообщества: -id сообщества.
        /// </param>
        /// <returns>Возвращает true, если метод был успешно выполнен. </returns>
        public async Task<bool> SetActivity(bool type, int? userId = null, int? peerId = null)
        {
            var query = new ApiQuery("messages.setActivity")
                .Add("type", type ? "typing" : null)
                .Add("peer_id", peerId)
                .Add("user_id", userId);

            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Позволяет установить фотографию мультидиалога, загруженную с помощью метода photos.getChatUploadServer.
        /// </summary>
        /// <param name="file">
        ///     Содержимое поля response из ответа специального upload сервера, полученного в результате загрузки
        ///     изображения на адрес, полученный методом photos.getChatUploadServer.
        /// </param>
        public async Task<ChatPhotoResult> SetChatPhoto(string file)
        {
            var query = new ApiQuery("messages.setChatPhoto")
                .Add("file", file);

            var resp = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !resp.ChildNodes.Any()
                           ? null
                           : new ChatPhotoResult(resp);
            }
            return null;
        }

        /// <summary>
        ///     Формирует список сообщений, при этом обновляя счетчики
        /// </summary>
        /// <param name="x">Xml-елемент с ответом</param>
        /// <param name="messageType">Тип сообщений</param>
        /// <param name="nodeName">Название Xml-елемента, дочерные елементы которого - сообщения</param>
        internal List<Message> BuildMessagesList(ApiNode x, MessageType messageType, string nodeName = null)
        {
            var count = x.Short("count");
            if (count.HasValue)
            {
                UpdateCounters(count.Value, messageType);
            }
            var nodes = x.SelectNodes("items/" + (string.IsNullOrWhiteSpace(nodeName) ? "*" : nodeName));
            if (nodes != null && nodes.Any())
            {
                return
                    x.SelectNodes("items/" + (string.IsNullOrWhiteSpace(nodeName) ? "*" : nodeName))
                     .Select(y => new Message(y))
                     .ToList();
            }
            return null;
        }

        /// <summary>
        ///     Обновление счетчиков
        /// </summary>
        /// <param name="msgCount">Количство сообщений</param>
        /// <param name="type">Тип сообщений</param>
        internal void UpdateCounters(short msgCount, MessageType type)
        {
            if (type == MessageType.History)
            {
                HistoryMessagesCount = msgCount;
            }
            if (type == MessageType.Dialogs)
            {
                UnreadDialogs = msgCount;
            }
            else
            {
                switch (type)
                {
                    case MessageType.Incoming:
                        IncomingMessagesCount = msgCount;
                        break;
                    case MessageType.Outgoing:
                        OutgoingMessagesCount = msgCount;
                        break;
                }
            }
        }
    }
}